﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LuckyLoot.Domain.Entities;
using LuckyLoot.Domain.Entities.People;
using LuckyLoot.Domain.Aggregate;
using LuckyLoot.Domain.ValueObjects;
using LuckyLoot.ServiceFacade.Contracts;
using LuckyLoot.Common.Repositories;
using LuckyLoot.Common.Contexts;
using LuckyLoot.Common.Validation;
using System.Data;
using System.Data.Entity;

namespace LuckyLoot.ServiceFacade.Implementation
{
    public class ApointmentService : IApointmentService
    {
        public Apointment ScheduleVisit(string title, string description, int customerID, int artistID, DateTime date, DateTime? returnDate)
        {
            var apointment = new Apointment();
            using (var repository = new ApplicationContext())
            {
                this.ValidateConflictingApointments(date, artistID, repository);

                apointment.Title = title;
                apointment.Description = description;
                apointment.Customer = repository.Customers.Where(c => c.Id.Equals(customerID)).First();
                apointment.Artist = repository.Users.Where(c => c.Id.Equals(artistID)).First() as TattooArtist;
                apointment.Date = date;
                apointment.ReturnDate = returnDate;

                repository.Apointments.Add(apointment);
                repository.SaveChanges();
            }
            return apointment;
        }

        public TattooApointment ScheduleTattoo(string title, string description, int customerID, int artistID, DateTime date, TattooSize size, TattooStyle style, TattooColors colors)
        {
            var tattoo = new TattooApointment(date);

            using (var repository = new ApplicationContext()) 
            {
                this.ValidateConflictingApointments(date, artistID, repository);

                tattoo.Title = title;
                tattoo.Description = description;
                tattoo.Customer = repository.Customers.Where(c => c.Id.Equals(customerID)).First();
                tattoo.Artist = repository.Users.Where(c => c.Id.Equals(artistID)).First() as TattooArtist;
                tattoo.Date = date; 
                tattoo.Size = size;
                tattoo.Style = style;
                tattoo.Colors = colors;

                repository.Apointments.Add(tattoo);
                repository.SaveChanges();
            }

            return tattoo;
        }

        public Apointment UpdateApointment(int apointmentID, string title, string description, int customerID, int artistID, DateTime date, DateTime? returnDate)
        {
            ValidState.Requires<ArgumentNullException>(apointmentID < 0);

            Apointment apointment;
            using (var repository = new ApplicationContext())
            {
                this.ValidateConflictingApointments(date, artistID, repository);
                
                apointment = repository.Apointments.Where(a => a.Id.Equals(apointmentID)).First();

                apointment.Title = title;
                apointment.Description = description;
                apointment.Customer = repository.Customers.Where(c => c.Id.Equals(customerID)).First();
                apointment.Artist = repository.Users.Where(c => c.Id.Equals(artistID)).First() as TattooArtist;
                apointment.Date = date;
                apointment.ReturnDate = returnDate;

                repository.Entry(apointment).State = EntityState.Modified;
                repository.SaveChanges();
            }
            return apointment;
        }

        public TattooApointment UpdateTattooApointment(int tattooID, string title, string description, int customerID, int artistID, DateTime date, TattooSize size, TattooStyle style, TattooColors colors)
        {
            ValidState.Requires<ArgumentNullException>(tattooID > 0);
            var customerService = new CustomerManagementService();
            var userService = new UserService();

            TattooApointment tattoo;
            using (var repository = new ApplicationContext())
            {
                this.ValidateConflictingApointments(date, artistID, repository);

                tattoo = repository.Apointments.Where(a => a.Id.Equals(tattooID)).First() as TattooApointment;

                tattoo.Title = title;
                tattoo.Description = description;
                tattoo.Customer = repository.Customers.Where(c => c.Id.Equals(customerID)).First();
                tattoo.Artist = repository.Users.Where(c => c.Id.Equals(artistID)).First() as TattooArtist;
                tattoo.Size = size;
                tattoo.Style = style;
                tattoo.Colors = colors;

                if (tattoo.Sessions.Count == 1)
                {
                    tattoo.Sessions[0].Date = date;
                }
                else
                    tattoo.Sessions.Add(new TattooSession() { Date = date });

                repository.Entry(tattoo).State = EntityState.Modified;
                repository.SaveChanges();
            }
            return tattoo;
        }

        private void ValidateConflictingApointments(DateTime date, int artistID, ApplicationContext repository)
        {
            var conflictingQuery = from tattooApp in repository.Apointments
                                   where tattooApp is TattooApointment && tattooApp.Artist.Id.Equals(artistID)
                                   select tattooApp;

            foreach (var apointment in conflictingQuery.ToList())
            {
                var tattooApointment = apointment as TattooApointment;

                if (tattooApointment.Sessions.Exists(s => s.Date.Date.Equals(date.Date) && s.Date.Hour.Equals(date.Hour)))
                    throw new InvalidOperationException(string.Format("Já existe um atendimento marcado no horário selecionado com o profissional {0}. Selecione outro horário.", tattooApointment.Artist.Name));
            }
        }

        public TattooApointment ConvertVisitToTattoo(int apointmentID)
        {
            TattooApointment tattoo;

            using (Repository<TattooApointment, ApplicationContext> repository = new Repository<TattooApointment, ApplicationContext>())
            {
                var apointment = this.GetApointment(apointmentID);

                tattoo = new TattooApointment(apointment.Date);
                tattoo.Id = apointment.Id;
                tattoo.Title = apointment.Title;
                tattoo.Date = apointment.Date;
                tattoo.Description = apointment.Description;
                tattoo.Customer = apointment.Customer;
                tattoo.Artist = apointment.Artist;

                repository.Add(tattoo);
                repository.Save();
            }

            return tattoo;
        }

        public TattooApointment ConvertVisitToTattoo(Apointment apointment, TattooColors colors, TattooSize size, TattooStyle style)
        {
            var tattoo = new TattooApointment(apointment.Date);
            tattoo.Id = apointment.Id;
            tattoo.Title = apointment.Title;
            tattoo.Description = apointment.Description;
            tattoo.Customer = apointment.Customer;
            tattoo.Artist = apointment.Artist;
            tattoo.Colors = colors;
            tattoo.Size = size;
            tattoo.Style = style;

            using (Repository<TattooApointment, ApplicationContext> repository = new Repository<TattooApointment, ApplicationContext>())
            {
                repository.Add(tattoo);
                repository.Save();
            }

            return tattoo;
        }

        public TattooApointment ScheduleTattooSession(int tattooApointmentID, DateTime date)
        {
            TattooApointment tattoo;

            using (var repository = new ApplicationContext())
            {
                tattoo = repository.Apointments.Where(a => a.Id.Equals(tattooApointmentID)).First() as TattooApointment;

                this.ValidateConflictingApointments(date, tattoo.Artist.Id, repository);

                tattoo.AddSession(date);

                repository.Entry(tattoo).State = EntityState.Modified;
                repository.SaveChanges();
            }
            
            return tattoo;
        }

        public decimal CalculateAverageTattooPrice(int tattooApointmentID)
        {
            var tattoo = this.GetTattooApointment(tattooApointmentID);
            var tattooColors = tattoo.Colors;
            var tattooSize = tattoo.Size;
            int tattooSessions = tattoo.Sessions.Count;

            return CalculateAverageTattooPrice(tattooColors, tattooSize, tattooSessions);
        }

        public decimal CalculateAverageTattooPrice(TattooColors tattooColors, TattooSize tattooSize, int tattooSessions)
        {
            int minValue = 50;
            int sessionCount;
            decimal sessionSizePoints = 0m;
            decimal colorPoints = 0m;
            decimal tattooValue = 0m;

            switch (tattooColors)
            { 
                case TattooColors.BlackWhite:
                    colorPoints = 2.2m;
                    break;
                case TattooColors.Black:
                    colorPoints = 2.4m;
                    break;
                case TattooColors.BlackRed:
                    colorPoints = 2.8m;
                    break;
                case TattooColors.Colorful:
                    colorPoints = 3m;
                    break;
            }


            switch (tattooSize)
            { 
                case TattooSize.Small:
                    sessionSizePoints = 35;
                    break;
                case TattooSize.Medium:
                    sessionSizePoints = 180;
                    if (tattooSessions > 1)
                        sessionSizePoints = sessionSizePoints + 50;
                    break;
                case TattooSize.Large:
                case TattooSize.HalfArmSleeve:
                    sessionCount = tattooSessions < 2 ? 2 : tattooSessions;
                    sessionSizePoints = sessionCount * 150;
                    break;
                case TattooSize.FullArmSleeve:
                case TattooSize.HalfLegSleeve:
                    sessionCount = tattooSessions < 4 ? 4 : tattooSessions;
                    sessionSizePoints = sessionCount * 150;
                    break;
                case TattooSize.FullLegSleeve:
                    sessionCount = tattooSessions < 8 ? 8 : tattooSessions;
                    sessionSizePoints = sessionCount * 145;
                    break;
                case TattooSize.ChestPiece:
                    sessionCount = tattooSessions < 3 ? 3 : tattooSessions;
                    sessionSizePoints = sessionCount * 100;
                    break;
                case TattooSize.BellyPiece:
                    sessionCount = tattooSessions < 3 ? 3 : tattooSessions;
                    sessionSizePoints = sessionCount * 105;
                    break;
                case TattooSize.ChestBellyFullPiece:
                    sessionCount = tattooSessions < 6 ? 6 : tattooSessions;
                    sessionSizePoints = sessionCount * 115;
                    break;
                case TattooSize.BackPiece:
                    sessionCount = tattooSessions < 5 ? 5 : tattooSessions;
                    sessionSizePoints = sessionCount * 120;
                    break;
                case TattooSize.Kimono:
                    sessionCount = tattooSessions < 20 ? 20 : tattooSessions;
                    sessionSizePoints = sessionCount * 210;
                    break;
                case TattooSize.FullBodySuit:
                    sessionCount = tattooSessions < 25 ? 25 : tattooSessions;
                    sessionSizePoints = sessionCount * 210;
                    break;
            }


            tattooValue = sessionSizePoints * colorPoints;

            if (tattooValue < minValue)
                return minValue;
            else return tattooValue;
        }

        public void UploadPicture(int apointmentID, byte[] picture)
        {
            Picture pic = new Picture();
            pic.Content = picture;

            using (var repository = new ApplicationContext())
            {
                var persistedApointment = repository.Apointments.Where(a => a.Id.Equals(apointmentID)).First();
                persistedApointment.AddPicture(pic);

                repository.Entry(persistedApointment).State = EntityState.Modified;
                repository.SaveChanges();
            }
        }

        public void UploadPictures(int apointmentID, List<byte[]> pictures)
        {
            using (var repository = new ApplicationContext())
            {
                var persistedApointment = repository.Apointments.Where(a => a.Id.Equals(apointmentID)).First();
                foreach (var picture in pictures)
                {
                    persistedApointment.AddPicture(new Picture() { Content = picture });
                }

                repository.Entry(persistedApointment).State = EntityState.Modified;
                repository.SaveChanges();
            }
        }

        public void DeleteAllPictures(Apointment apointment)
        {
            using (Repository<Apointment, ApplicationContext> repository = new Repository<Apointment, ApplicationContext>())
            {
                using (Repository<Picture, ApplicationContext> pictureRepository = new Repository<Picture, ApplicationContext>())
                {
                    foreach (var pic in apointment.Pictures)
                    {
                        pictureRepository.Delete(pic);
                    }

                    pictureRepository.Save();
                }


                apointment.Pictures.Clear();


                repository.Edit(apointment);
                repository.Save();
            }
        }

        public void DeletePicture(Apointment apointment, Picture picture)
        {
            apointment.Pictures.Remove(picture);

            using (Repository<Picture, ApplicationContext> pictureRepository = new Repository<Picture, ApplicationContext>())
            {
                pictureRepository.Delete(picture);

                pictureRepository.Save();
            }

            using (Repository<Apointment, ApplicationContext> repository = new Repository<Apointment, ApplicationContext>())
            {
                repository.Edit(apointment);
                repository.Save();
            }
        }

        public void DeletePicture(Apointment appointment, int photoId)
        {
            using (Repository<Picture, ApplicationContext> pictureRepository = new Repository<Picture, ApplicationContext>())
            {
                var picture = pictureRepository.GetById(photoId);

                pictureRepository.Delete(picture);

                pictureRepository.Save();
            }
        }

        public Apointment GetApointment(int apointmentID)
        {
            ValidState.Requires<ArgumentNullException>(apointmentID > 0);

            Apointment apointment = null;
            Repository<Apointment, ApplicationContext> repository = new Repository<Apointment, ApplicationContext>();
            apointment = repository.GetById(apointmentID);
            ValidState.Requires<InvalidOperationException>(apointment != null, string.Format("Não foi possível encontrar a visita de id {0}.", apointmentID));
            ValidState.Requires<InvalidOperationException>(!(apointment is TattooApointment), "Este registro é um agendamento de tatuagem e é invalido neste contexto.");
            return apointment;
        }

        public TattooApointment GetTattooApointment(int tattooApointmentID)
        {
            ValidState.Requires<ArgumentNullException>(tattooApointmentID > 0);

            TattooApointment apointment = null;
            Repository<TattooApointment, ApplicationContext> repository = new Repository<TattooApointment, ApplicationContext>();
            apointment = repository.GetById(tattooApointmentID);
            ValidState.Requires<InvalidOperationException>(apointment != null, string.Format("Não foi possível encontrar o agendamento de tatuagem de id {0}.", tattooApointmentID));
            ValidState.Requires<InvalidOperationException>((apointment is TattooApointment), "Este registro é um agendamento de visita e é invalido neste contexto.");

            return apointment;
        }

        public List<Apointment> GetAllApointments()
        {
            List<Apointment> apointments = new List<Apointment>();
            using (Repository<Apointment, ApplicationContext> repository = new Repository<Apointment, ApplicationContext>())
            {
                var allApointments = repository.GetAll().ToList<Apointment>();

                foreach (var apointment in allApointments)
                {
                    if (!(apointment is TattooApointment))
                        apointments.Add(apointment);
                }
            }

            return apointments;
        }

        public List<TattooApointment> GetAllTattooApointments()
        {
            List<TattooApointment> tatooApointments = null;
            using (Repository<TattooApointment, ApplicationContext> repository = new Repository<TattooApointment, ApplicationContext>())
            {
                tatooApointments = repository.GetAll().ToList<TattooApointment>();
            }

            return tatooApointments;
        }

        public List<Picture> GetApointmentPictures(int apointmentID)
        {
            using (var repository = new ApplicationContext())
            {
                var persistedApointment = repository.Apointments.Where(a => a.Id.Equals(apointmentID)).First();

                return persistedApointment.Pictures;
            }
        }

    }
}
